package domain

import (
	"k8s.io/apimachinery/pkg/util/sets"

	"cvevulner/util"
)

type Cves []Cve

// CvesByComponent is group of cves by component
type CvesByComponent []Cve

// CvesByVersion is group of CvesByComponent by version
type CvesByVersion []Cve

type Cve struct {
	Component       string
	ComponentDesc   string
	SeverityLevel   string
	AffectedVersion []string
	AffectedProduct string
	OpeneulerScore  float64
	Theme           string
	CveNum          string
	CveBrief        string
	OpeneulerVector string
	CveVersion      string
	HotIssueNum     string
	AbiVersion      string
	ColdIssue       Issue
}

type Issue struct {
	Number string
	Status string
	Repo   string // in src-openeuler, repo == package, name == component
}

func (c *Cve) isAffectVersion(version string) bool {
	for _, v := range c.AffectedVersion {
		if v == version {
			return true
		}
	}

	return false
}

func (c *Cve) SetAffectVersionWithIntersection(branches []string) {
	s1 := sets.NewString(c.AffectedVersion...)
	s2 := sets.NewString(branches...)
	c.AffectedVersion = s1.Intersection(s2).List()
}

// FilterAffectVersion 只处理需要发布公告的版本分支
func (cs Cves) FilterAffectVersion() {
	for k, v := range cs {
		affectVersionSet := sets.New(v.AffectedVersion...)
		cs[k].AffectedVersion = maintainVersion.Intersection(affectVersionSet).UnsortedList()
	}
}

func (cs Cves) GroupByVersion() map[string]CvesByVersion {
	group := make(map[string]CvesByVersion)
	for _, cve := range cs {
		group[cve.AffectedVersion[0]] = append(group[cve.AffectedVersion[0]], cve)
	}

	return group
}

// GroupByComponent group cves by component
func (cs Cves) groupByComponent() map[string]CvesByComponent {
	group := make(map[string]CvesByComponent)
	for _, d := range cs {
		group[d.Component] = append(group[d.Component], d)
	}

	return group
}

// GenerateBulletins CvesByComponent is a component-differentiated set of cves,
// Bulletins are consolidated into one when all issues of a component affect all versions currently maintained,
// otherwise they are split into multiple bulletins by version
func (cs Cves) GenerateBulletins() []SecurityBulletin {
	var securityBulletins []SecurityBulletin

	for _, dsc := range cs.groupByComponent() {
		if dsc.isCombined() {
			securityBulletins = append(securityBulletins, dsc.combinedBulletin())
		} else {
			securityBulletins = append(securityBulletins, dsc.separatedBulletins()...)
		}
	}

	return securityBulletins
}

// IsCombined determine whether multiple cves under the same component
// need to be combined into a single bulletin
func (cbc CvesByComponent) isCombined() bool {
	for _, d := range cbc {
		if len(d.AffectedVersion) != len(maintainVersion) {
			return false
		}

		for _, version := range d.AffectedVersion {
			if !maintainVersion.Has(version) {
				return false
			}
		}
	}

	return true
}

// CombinedBulletin put all cves in one bulletin
func (cbc CvesByComponent) combinedBulletin() SecurityBulletin {
	return SecurityBulletin{
		AffectedVersion: cbc[0].AffectedVersion,
		Date:            util.Date(),
		Component:       cbc[0].Component,
		Cves:            Cves(cbc),
	}
}

// SeparatedBulletins split into multiple bulletins by version
func (cbc CvesByComponent) separatedBulletins() []SecurityBulletin {
	var sbs []SecurityBulletin
	for version, ds := range cbc.separateByVersion() {
		sbs = append(sbs, ds.bulletinByVersion(version))
	}

	return sbs
}

func (cbc CvesByComponent) separateByVersion() map[string]CvesByVersion {
	classifyByVersion := make(map[string]CvesByVersion)
	for version := range maintainVersion {
		for _, d := range cbc {
			if d.isAffectVersion(version) {
				classifyByVersion[version] = append(classifyByVersion[version], d)
			}
		}
	}

	return classifyByVersion
}

func (cbv CvesByVersion) bulletinByVersion(version string) SecurityBulletin {
	return SecurityBulletin{
		AffectedVersion: []string{version},
		Date:            util.Date(),
		Component:       cbv[0].Component,
		Cves:            Cves(cbv),
	}
}
